#ifndef FILE_ProducerConsumerSwap_HPP
#define FILE_ProducerConsumerSwap_HPP

#include "System.hpp"
#include <SFML/System.hpp>

#include "thread.hpp"
#include "Debug.hpp"

/********************************************//**
 * Synchronize and object between and parallelize consumer and producer threads.
 *
 * For this, it uses two objects, one for the producer
 * and one for the consumer. While the producer fills
 * the first one, the consumer uses the other, and then 
 * they sychronize when they are done using it, the objects are
 * swaped, the producer fills the other object while the consumer
 * uses the one the other just filled.
 *
 * Even tough the consumer is called consumer, he can modify
 * the object too. This may be usefull, given the objects
 * are not reconstructed (constructor not called) once used.
 * 
 * The objects are constructed with their default
 * constructor. If another behaviour is required, the
 * producer should take care of it, and/or a wrapper object
 * should be used.
 *
 * \warning Only one producer and one consumer can exist
 * (ie : max 2 calls to ***Swap() at once).
 * \todo add a function/flag \c tryWait , to attempt
 * earlier synchronizing without being blocking.
 ***********************************************/
template<typename RessourceType>
class se::System::ProducerConsumerSwap{
	public:
		/********************************************//**
		 * Creates an instance of a ProducerConsumerSwap.
		 ***********************************************/
		ProducerConsumerSwap():
			m_firstTime(true),
			m_ressourceProd(NULL),
			m_ressourceCons(NULL),
			m_swap_b1(false),
			m_swap_b2(true){
			SE_REMOVE_ON_RELEASE(m_sanity_userCount = 0;)
			m_ressourceProd = new RessourceType();
			m_ressourceCons = new RessourceType();
		}
		
		/********************************************//**
		 * Returns the producer's ressource.
		 *
		 * When the ressource is produced, the method ProducerSwap()
		 * must be called.
		 * \return The ressource.
		 * \see void ProducerSwap()
		 ***********************************************/
		RessourceType* ProducerGet(){
			return m_ressourceProd;
		}
		
		/********************************************//**
		 * Tells that the producer is done.
		 *
		 * Then wait the consumer to be done, or if he
		 * already is, swap the stuuf !.
		 ***********************************************/
		void ProducerSwap(){
			WaitSwap();
		}
		
		/********************************************//**
		 * Returns the consumers's ressource.
		 *
		 * When the ressource is consumed, the method ConsumerSwap()
		 * must be called.
		 *
		 * Please note that the method waits for an item to be
		 * produced before returning.
		 * \return The ressource.
		 * \see void ConsumerSwap()
		 ***********************************************/
		RessourceType* ConsumerGet(){
			// Need a first item produced.
			if(m_firstTime){
				ConsumerSwap();
			}
			return m_ressourceCons;
		}

		/********************************************//**
		 * Tells that the consumer is done.
		 *
		 * Then wait the consumer to be done, or if he
		 * already is, swap the stuuf !.
		 ***********************************************/
		void ConsumerSwap(){
			WaitSwap();
		}
		
	protected:
		/********************************************//**
		 * Swaps the consumer ressource and the producer's one.
		 ***********************************************/
		void Swap(){
			RessourceType* tmp = m_ressourceProd;
			m_ressourceProd = m_ressourceCons;
			m_ressourceCons = tmp;
		}
		/********************************************//**
		 * Either ProducerSwap or ConsumerSwap.
		 *
		 * Internally, the behaviour is the same.
		 ***********************************************/
		void WaitSwap(){
			boost::unique_lock<boost::mutex> lk(m_swapMutex);
			//m_swapMutex.lock();
			SE_REMOVE_ON_RELEASE(
				++m_sanity_userCount;
				assert(m_sanity_userCount<=2);
			)
			if(m_swap_b1){
				m_swap_b1 = false;
				m_firstTime = false;
				Swap();
				m_swaped_1.notify_one();
				m_swap_b2 = false;
				while(!m_swap_b2){
					m_swaped_2.wait(lk);
				}
			}else{
				m_swap_b1 = true;
				while(m_swap_b1){
					m_swaped_1.wait(lk);
				}
				m_swap_b2 = true;
				m_swaped_2.notify_one();
			}
			SE_REMOVE_ON_RELEASE(
				--m_sanity_userCount;
			)
			//m_swapMutex.unlock();
		}
		bool m_firstTime;
		RessourceType* m_ressourceProd;
		RessourceType* m_ressourceCons;
		ThreadSignal_t m_swap_b1;
		ThreadSignal_t m_swap_b2;
		SE_REMOVE_ON_RELEASE(volatile uint8_t m_sanity_userCount;)
		boost::mutex m_swapMutex;
		boost::condition_variable m_swaped_1;
		boost::condition_variable m_swaped_2;
};

#endif // FILE_ProducerConsumerSwap_HPP

